package com.cskaoyan.javase.object._2equals;

/**
 * @description: equals方法
 * @author: wuguidong@cskaoyan.onaliyun.com
 **/

/**
 * equals方法的声明:
 *      public boolean equals(Object obj)
 *
 * 需要注意的是:
 *      1.方法有返回值,返回值是布尔类型
 *      2.方法需要传参,传入的参数Object对象,意味着可以传入所有的对象
 *
 * equals方法的作用:
 *      equals是等于的意思,
 *          所以该方法就用于判断方法调用者对象和方法传入的对象是否相等,如果相等就返回true,否者返回false
 *
 * Object类当中的equals方法的实现(Object类认为的对象相等条件):
 *       return (this == obj);
 *      Object类认为的对象相等是无条件两个引用指向同一个对象,这个相等的条件是很苛刻的
 *
 * 我们认为的两个对象相等:
 *      1.如果两个对象根本就不是同一个类型的对象,没有可比性,直接认定不相等
 *      2.如果两个对象是相同类型的对象(意味着方法/行为是一致的):
 *          a.如果成员变量的取值(属性)是一致的,那么就认为对象相等
 *          b.如果成员变量的取值(属性)不相同,那么对象就不相等
 *
 * 显然Object类当中的默认实现过于苛刻,我们需要自己重写该方法
 * equals方法的重写,在JDK文档上有固定的标准,称之为"设计equals() 方法的原则（常规协定）"
 * 满足以下条件:
 *      1.自反性：对于任何非空引用值 x，x.equals(x) 都应返回 true
 *      自己和自己比,应该返回true
 *
 *      2.排他性：当比对的不是同种类型的对象或者是一个null时，默认返回false
 *
 *
 *      3.对称性：对于任何非空引用值 x 和 y，当且仅当 y.equals(x) 返回 true 时，x.equals(y) 才应返回 true
 *      x==y --> y==x
 *
 *      4.传递性：对于任何非空引用值 x、y 和 z，如果 x.equals(y) 返回 true，并且 y.equals(z) 返回 true
 *      那么x.equals(z) 应返回 true
 *      x==y,y==z --> x==z
 *
 *      5.一致性：对于任何非空引用值 x 和 y，多次调用 x.equals(y) 始终返回 true 或始终返回 false
 *      前提是对象上equals 比较中所用的信息没有被修改
 *      如果比较的条件没有修改,那么无论比较多少次,结果都应该是一样的
 *
 *      3,4,5这三个条件,只需要比较的条件是一个取值,就很容易满足
 *      如果使用成员变量的取值(属性)来判断对象相等,就直接满足3,4,5三个条件
 *
 * 以上就完成了equals方法的重写,我们比较两个对象的成员变量取值来确定对象是否相等
 * 注意事项:
 *      1.equals方法的排他性有两种写法:
 *          a.getClass()方法:
 *              比较的是类对象,只有两个对象的运行时Class对象完全是一个即完全是同种类型对象时,才相等
 *          b.instanceof关键字
 *              引用 instanceof 类名
 *              判断引用所指向的对象是否是后面类名的对象或者子类对象,它比getClass方法要更加宽松,允许子类对象
 *              并且null instanceof 任何类 结果都是false
 *
 *      2.equals方法可以用快捷键alt+insert自动生成,以后的开发如果没有特殊要求,直接自动生成即可,无需手写
 *
 *      3.浮点数的比较,多数情况下用"=="之类的是不会有任何问题的.但是浮点数有NaN(非数),无穷大,无穷小等概念,仅仅用"=="就无法判断了.
 *      所以浮点数的比较大小,建议使用Double或者Float包装类型中的compare(p1,p2)方法
 *      该方法的返回值是一个int类型整数,如果p1>p2,就返回1(正数),如果p1<p2,就返回-1(负数),如果p1==p2返回0
 *
 *      4.对于任何非空引用值 x，x.equals(null) 都应返回 false
 *      不要使用一个null常量去调用方法，会引发程序错误，报空指针异常
 *         在方法中，我们只能对方法的参数进行校验，没办法校验调用者
 *         应该在外部写代码，防止使用一个null去调用方法
 *         如果类中有引用类型的成员变量，继续调用该引用类型的equal()方法判断
 *              如果有必要,需要自己重写它的equals方法
 *
 *
 */
public class Demo {
    public static void main(String[] args) {
        /*//创建两个学生对象,不是同一个对象,但是成员变量的取值一样
        Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        //未重写equals方法之前是false,重写后是true
        System.out.println(s1.equals(s2));
        System.out.println(s1 == s2);//false

        StudentSon s3 = new StudentSon(18, 100);
        System.out.println(s3.equals(s1));*/

        /*double a1 = 0.1;
        double a2 = 0.1;
        System.out.println(a1 == a2);

        double d1 = Double.NaN;
        double d2 = Double.NaN;
        System.out.println(d1 == d2);
        System.out.println(Double.compare(d1, d2));*/

        Student s1 = new Student(18, 100, null);
        Student s2 = new Student(18, 100, "张三");
        System.out.println(s1.equals(s2));
    }
}

class Student {

    int age;
    double score;
    String name;
    //Star s;

    public Student() {
    }

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    public Student(int age, double score, String name) {
        this.age = age;
        this.score = score;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }

    //重写equals方法
    @Override
    public boolean equals(Object obj) {
        //1.满足自反性
        //必须判断是一个对象,然后返回true
        if (obj == this) return true;

        //2.排它性
        //如果传入的对象类型不一致或者是null就无需比较了,直接返回false
        //2.1 getClass实现排它性
        if (obj == null || obj.getClass() != this.getClass()) return false;
        //2.2 instanceof实现排它性
        //if (!(obj instanceof Student)) return false;

        //3.对称性,传递性,一致性只需要使用成员变量的取值判断,就直接满足
        //以下开始判断成员变量的取值
        //当代码运行到这里,obj对象一定是Student对象,可以直接做强转.无需判断
        Student target = (Student) obj;
        //3.1 比较age
        if (this.age != target.age) return false;
        //3.2 比较score
        //return this.score == target.score;
        if (Double.compare(this.score, target.score) != 0) return false;

        //3.3 比较name
        if (this.name == null) return target.name == null;
        //if (this.name == null){
        //    if (target.name == null) return true;
        //    else return false;
        //}
        return this.name.equals(target.name);
    }

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }*/

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }
}

class StudentSon extends Student {
    public StudentSon() {
    }

    public StudentSon(int age, double score) {
        super(age, score);
    }
}
//class Star{
//    int age;
//}
